Simple Pages パターン
https://scrapbox.io/files/60f2a547d35bb7001d69967d.png
コンセプト
画面設計はページ単位
単純な1階層パスルーティングで構成する
<router-outlet> を入れ子にしない
個々のページの具体的要求から横断的関心事を取り出して再利用可能にする
基本構造
code:tree
app
├ app-routing.module.ts
├ app.module.ts
├ app.component.ts
├ pages
│ ├ products
│ │ ├ products.module.ts
│ │ └ products.component.ts
│ ├ product-detail/...
│ ├ cart/...
│ └ shipping/...
└ shared
├ product.service.ts
├ cart.service.ts
└ product-list
├ product-list.module.ts
└ product-list.component.ts
アプリケーションルート直下には pages ディレクトリと shared ディレクトリが置かれる
pages ディレクトリには各ページに対応するモジュールのディレクトリが置かれる
pages を設けずに直接ページを配置するケースもある
shared ディレクトリには再利用可能なサービスやコンポーネントなどが置かれる
設計意図
ページ単位で画面設計がなされるようなプロジェクトにおいて、アプリケーション内での横断的関心事はそれほど増えないという経験則に基づく
そのようなプロジェクトではページそのものが機能要件として仕様定義される傾向にある
新しい機能の追加もページの追加という形で行われることが多い
変更や修正もページを対象にされることが多い
ページとユースケースが密結合している
あるページへの変更が別のページへ影響を与えないことが開発のアジリティを高めるファクターになる
同じユースケースに関連するモジュールをひとまとまりにする
明確に定義されたページごとの要件を満たしながら、実装が重複する横断的関心事を見つけていくアプローチをとる
まずPageだけが生まれ、その後に必要に応じてSharedが育っていく
漸進的なリファクタリングを前提とする
依存方向の制約
ページ間の直接の依存は禁止される
横断的関心事は必ずSharedを介する
Sharedからページへの依存は禁止される
何かに依存されるモジュールは安定度が高くなる(変えにくくなる)
ユースケース(変わりやすいもの)に基づく各ページの安定度を低く保つ
アジャイルなプロダクト開発においてはユースケースレイヤーを変えやすく保つことが重要になる
考慮事項
似た構造を持つページが多数発生する場合、ページごとに個別に記述されるモジュールの多さがデメリットになる
例: 汎用的なCRUDユースケースに対してリソース種別ごとにページが生まれるようなケース
いわゆるデータ管理画面、リソースごとにMaster/Detail画面が生まれるようなもの
必要になるコンセプト
機能ベースでのユースケース抽象化
ページとユースケースを疎結合にする
モジュールの再利用範囲
すべてのページで再利用したいモジュールと、いくつかの限られたページで共有したいモジュールの扱いが同じ
機能という単位がない
Shared内での構造化の指針が必要になる
詳細構造
code:ページモジュール
app
└ pages
└ products
├ products.module.ts
├ products.component.ts
├ products.usecase.ts
├ products.store.ts
└ components
├ products-search
│ └ products-search.component.ts
└ product-list-container
└ product-list-container.component.ts
ページに閉じた関心事はページモジュール内に配置する(閉鎖性共通の原則)
ページコンポーネント
ルーティングの切り替え単位になるコンポーネント
ページコンポーネントはユースケース層とプレゼンテーション層のコミュニケーションのために複雑になりやすい
対策
ユースケースサービスへの移譲 products.usecase.ts
ビジネスロジックの切り出し
コンテナコンポーネントへの移譲 product-list-container.component.ts
UI関心の切り出し
結果的にページコンポーネントに残る責務
URL由来の情報 (ActivatedRoute) をページの状態に変換すること
ページに紐づくユースケースのライフサイクルを管理すること
code:Shared
app
└ shared
├ shared.module.ts
├ api
│ ├ products.service.ts
│ ├ cart.service.ts
│ └ model.ts
├ logger
│ ├ logger.service.ts
│ └ index.ts
└ product-list
├ index.ts
├ product-list.module.ts
├ product-list.component.ts
└ product-list-item.component.ts
ユースケースに依存しないモジュール、あるいは横断的なユースケースに依存するモジュールを配置する
バックエンドAPIの呼び出しサービス
変更を求めるアクターはバックエンドAPI(のスキーマ)のみ
ユーティリティサービス
Loggerなどユースケースに依らないもの
アプリケーションウィジェット(UIコンポーネント)
アプリケーション内での再利用可能なUIコンポーネント
アプリケーション間で再利用可能なUIコンポーネントはライブラリとして外部化される
ページのユースケースと関連はしながらも依存はしていない場合に切り出される
ページのユースケースに依存する場合はページモジュール内に配置される
複数のページに表示されるウィジェット的コンポーネントが該当しやすい